package aliyun

import (
	//接口的定义引进来
	"fmt"
	"os"

	"gitee.com/libgitee/go-course-go7/store"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
)

var (
	//声明一个对象,类型是接口, 变量的名字我们不要 但必须符合这个接口
	//对象是否实现了接口的约束
	//如果为我们改成Upload1就不满足我们store.Uploader这个接口的约束 
	// 如何校验我们的对象满不满足我们对应的一个接口的定义 

	// a string = "abc" 
	// _ store.Uploader  我不需要这个变量的值 只是做变量类型的判断
	// &AliOssStore{} 这个对象必须满足store.Uploader这个类型
	// _ store.Uploader = &AliOssStore{} 声明了一个对象 至少需要一个地址 如果想指针都不需要 
	// nil是空指针 是有类型的 
	// a *AliOssStore = nil  nil是一个AliOssStore的指针
	// 如何把nil转换成一个指定类型的变量
	// a int = 16
	// b int64 = int64(a)    int64类型(指针)   
	// (int64类型)(指针)
	// (*AliOssStore)(nil) 获得了这个类型的空指针
	// 断言指针符合这个接口约束
	// _ store.Uploader = (*AliOssStore)(nil)
	_ store.Uploader = &AliOssStore{}
)

// 有的时候传递的参数会改变 每次所有函数都需要加一次
type Option struct {  //?
	EndPoint string 
	AccessKey string 
	AccessSecret string 
}

//接收者  类似func (d dog)wang(){}
func (o *Option)Vlidate() error {   
		//参数校验, 因为如果不传参的话测试用例还是可以通过 添加参数校验就可以测试出来了
		if o.EndPoint == "" || o.AccessKey == "" || o.AccessSecret == "" {
			return fmt.Errorf("endpoint accessKey accessSecret has one empty")
		}
		return nil 
}

func NewDefaultAliOssStore()(*AliOssStore, error){
	return NewAliOssStore(&Option{
		EndPoint: os.Getenv("ALI_OSS_ENDPOINT"),
		AccessKey: os.Getenv("ALI_AK"),
		AccessSecret: os.Getenv("ALI_SK"),
	})
}
//4. AliOssStore对象的构造函数, 引入interface, 需要为函数写个构造函数 
func NewAliOssStore(opts *Option ) (*AliOssStore, error) {
	//校验参数
	if err  := opts.Vlidate(); err != nil {
		return  nil, err  //?
	}
	c, err := oss.New(opts.EndPoint, opts.AccessKey, opts.AccessSecret)
	if err != nil {
		panic(err)
	}
	return &AliOssStore{
		client: c,
		// a6  初始化的时候所以我们也要传递进来 传一个默认的对象进来 就会基于listener做一个进度条的展示
		// 
		listener: NewDefaultProgressListener(),
	},nil 
}

 
//2.面向对象第一步是构造对象
//因为我们的阿里云Oss client ,私有变量, 不允许外部访问  因为别人如果能拿到你的client对象 那别人就啥都能搞
//你不能控制行为了  封装是为了整个逻辑 可控  有了这个以后要产生一个对象必须通过构造函数来构造  不能直接构造 
// 比如ali.client是没法直接用的  所以必须用构造函数来构造它来使用
/*
比如 AliOssStore{A: "XXX"} map nil 
通过构造函数生成的AliOssStore,很多初始化的逻辑 都集中于构造函数NewAliOssStore 这样以后有修改 只要
修改构造函数就可以了, 不用到处去修改 所以一定要封装好

*/
type AliOssStore struct {
	client *oss.Client
	// a3 依赖listener的实现  怎么去实现  这是个接口?我们先把它先用上
	listener oss.ProgressListener
}

//3.实现接口  那么如何判断我们的对象有没有实现接口
func (s *AliOssStore)Upload(bucketName string, objectKey string, fileName string) error  {
	//2. 获取bucket
	bucket, err := s.client.Bucket(bucketName)
	if err != nil {
		return err
	}
	//3. 上传文件
	fmt.Println("#################")
	fmt.Println(bucketName,objectKey,fileName)
	// 补上listener以后 这个事件需要我的listener处理
	// 阿里的sdk有个option区域, 这个函数有个option区域 我们需要传递个函数给它 具体是需要传递一个object给这个函数 
	// object必须实现 progresschange方法  
	// 这个函数在上传的时候如果有文件大小变化会回调object的方法 这里是封装成接口的方式进行回调 把上传过程中的事件传给你
	// 核心就是我们去实现一个监听器  到时候把事件扔给这个监听器 然后根据事件里的数据 开始 上传中 结束 我们自己做一个进度条

	// a4 我们把它传进去 逻辑补上了 这个事件需要我们a3的listener处理  我们listener还没有实现
	err = bucket.PutObjectFromFile(objectKey,fileName, oss.Progress(s.listener))
	if err != nil {
		return err

	}
	fmt.Printf("文件: %s上传成功\n", fileName)

	// 4. 打印上传链接
	downloadUrl, err := bucket.SignURL(fileName, oss.HTTPGet, 60 * 60 * 24)
	if err != nil {
		return err 
	}
	fmt.Printf("下载链接: %s\n", downloadUrl)
	fmt.Println("\n注意: 文件下载有效期为1天, 中转站保存时间为3天, 请及时下载")

	return nil 
}


/*
为什么有构造函数:
因为我们的阿里云Oss client ,私有变量, 不允许外部访问



*/